本文同步更新於blog
情境:目前提供旅遊行程的方式
<?php
namespace App\BuilderPattern\Vacation;
class Program
{
/**
* @return array
*/
public function getDomesticTravel()
{
//高速鐵路一日體驗
return [
'from' => 'Kaohsiung',
'to' => 'Taipei',
'day' => 1,
'transport' => 'High Speed Rail'
];
}
/**
* @return array
*/
public function getInternationalTravel()
{
//東京五日遊
return [
'from' => 'Kaohsiung',
'to' => 'Tokyo',
'day' => 5,
'transport' => 'Airplane',
'hotel' => 'Disney Hotel'
];
}
}
老闆希望我們能提供更簡便的方式,來規劃不同的旅遊行程。
讓我們用建造者模式改造它。
需求一:實作旅遊行程 (產品類別)
<?php
namespace App\BuilderPattern\Vacation;
class Itinerary
{
/**
* @var string
*/
protected $from;
/**
* @var string
*/
protected $to;
/**
* @var int
*/
protected $day;
/**
* @var string
*/
protected $hotel;
/**
* @var string
*/
protected $transport;
/**
* @param string $name
* @param string|int $value
*/
public function __set($name, $value)
{
$this->$name = $value;
}
/**
* @param string $name
* @return string|int
*/
public function __get($name)
{
return $this->$name;
}
/**
* @return array
*/
public function toArray()
{
$result = get_object_vars($this);
foreach ($result as $name => $value) {
if (is_null($value)) {
unset($result[$name]);
}
}
return $result;
}
}
主要都是getter與setter方法。
當行程規劃好時,我們會透過 toArray() 方法來輸出。
需求二:實作行程建造者 (建造者類別)
<?php
namespace App\BuilderPattern\Vacation\Contracts;
use App\BuilderPattern\Vacation\Itinerary;
interface ItineraryPlanable
{
public function from(string $from): self;
public function to(string $to): self;
public function spendDays(int $day): self;
public function stayAt(string $hotel): self;
public function travelBy(string $transport): self;
public function getItinerary(): Itinerary;
}
<?php
namespace App\BuilderPattern\Vacation;
use App\BuilderPattern\Vacation\Itinerary;
use App\BuilderPattern\Vacation\Contracts\ItineraryPlanable;
class ItineraryBuilder implements ItineraryPlanable
{
/**
* @var Itinerary
*/
protected $itinerary;
public function __construct()
{
$this->itinerary = new Itinerary();
}
/**
* @param string $from
* @return self
*/
public function from(string $from): self
{
$this->itinerary->from = $from;
return $this;
}
/**
* @param string $to
* @return self
*/
public function to(string $to): self
{
$this->itinerary->to = $to;
return $this;
}
/**
* @param integer $day
* @return self
*/
public function spendDays(int $day): self
{
$this->itinerary->day = $day;
return $this;
}
/**
* @param string $hotel
* @return self
*/
public function stayAt(string $hotel): self
{
$this->itinerary->hotel = $hotel;
return $this;
}
/**
* @param string $transport
* @return self
*/
public function travelBy(string $transport): self
{
$this->itinerary->transport = $transport;
return $this;
}
/**
* @return Itinerary
*/
public function getItinerary(): Itinerary
{
return $this->itinerary;
}
}
行程建造者用了流式接口 (Fluent Interface),來增加程式碼可讀性。
我們待會會在指揮者類別中展示。
(註:此處也可以實作多個不同的行程建造者,來固定某些行程選項)
需求三:實作旅行社(指揮者類別)
<?php
namespace App\BuilderPattern\Vacation;
use App\BuilderPattern\Vacation\Contracts\ItineraryPlanable;
class TravelAgency
{
/**
* @var ItineraryPlanable
*/
protected $itineraryBuilder;
public function __construct(ItineraryPlanable $itineraryBuilder)
{
$this->itineraryBuilder = $itineraryBuilder;
}
/**
* @return array
*/
public function getHighSpeedRailItinerary()
{
$itinerary = $this->itineraryBuilder
->from('Kaohsiung')
->to('Taipei')
->travelBy('High Speed Rail')
->spendDays(1)
->getItinerary();
return $itinerary->toArray();
}
/**
* @return array
*/
public function getFiveDaysTokyoItinerary()
{
$itinerary = $this->itineraryBuilder
->from('Kaohsiung')
->to('Tokyo')
->travelBy('Airplane')
->spendDays(5)
->stayAt('Disney Hotel')
->getItinerary();
return $itinerary->toArray();
}
}
透過旅行社 (指揮者類別),我們封裝了行程的實作。
使得客戶端不用知道行程的建造過程。
<?php
namespace App\BuilderPattern\Vacation;
use App\BuilderPattern\Vacation\TravelAgency;
use App\BuilderPattern\Vacation\ItineraryBuilder;
class Program
{
/**
* @return array
*/
public function getDomesticTravel()
{
//高速鐵路一日體驗
$itineraryBuilder = new ItineraryBuilder();
$travelAgency = new TravelAgency($itineraryBuilder);
return $travelAgency->getHighSpeedRailItinerary();
}
/**
* @return array
*/
public function getInternationalTravel()
{
//東京五日遊
$itineraryBuilder = new ItineraryBuilder();
$travelAgency = new TravelAgency($itineraryBuilder);
return $travelAgency->getFiveDaysTokyoItinerary();
}
}
[單一職責原則]
我們將指揮者類別、建造者類別與產品類別,視為三種不同的職責。
由旅行社指揮行程建造者來構建行程。
[開放封閉原則]
當新增/修改行程時,我們只要調整指揮者類別。
當新增/修改行程內部的邏輯時,我們僅需修改產品類別。
[依賴反轉原則]
指揮者類別依賴於抽象的建造者介面。
建造者類別實作抽象的建造者介面。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:核心精神在於分離建造過程與產品本身的邏輯。